package com.github.jaceko.circuitswitcher.it.jaxws;
import com.github.jaceko.circuitswitcher.it.AbstractIntegrationTest;
import com.github.jaceko.circuitswitcher.it.jaxws.client.hello_world_soap_http.Greeter;
import com.github.jaceko.circuitswitcher.it.util.mock.WebserviceMockControler;
import com.github.jaceko.circuitswitcher.it.util.mock.WebserviceOperation;
import org.apache.cxf.clustering.CircuitSwitcherClusteringFeature;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.ws.WebServiceException;
import java.io.IOException;
import static com.github.jaceko.circuitswitcher.it.util.mock.SayHiResponseBuilder.sayHiResponse;
import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasXPath;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
public class JaxwsFailoverIntegrationTest extends AbstractIntegrationTest {
private static final String NODE1_ENDPOINT_ADDRESS = "http://localhost:9090/mock/services/SOAP/hello-world/endpoint";
private static final String NODE2_ENDPOINT_ADDRESS = "http://localhost:9191/mock/services/SOAP/hello-world/endpoint";
private WebserviceMockControler node1Controller = new WebserviceMockControler("http://localhost:9090");
private WebserviceMockControler node2Controller = new WebserviceMockControler("http://localhost:9191");
WebserviceOperation sayHiOperation = new WebserviceOperation("hello-world", "sayHi");
private JaxWsProxyFactoryBean bean;
public JaxwsFailoverIntegrationTest() {
bean = new JaxWsProxyFactoryBean();
bean.setServiceClass(Greeter.class);
}
private CircuitSwitcherClusteringFeature createCircuitBreakerFeature() {
CircuitSwitcherClusteringFeature cbff = new CircuitSwitcherClusteringFeature();
cbff.setAddressList(asList(NODE1_ENDPOINT_ADDRESS, NODE2_ENDPOINT_ADDRESS));
return cbff;
}
@Before
public void init() {
node1Controller.webserviceOperation(sayHiOperation).init();
node2Controller.webserviceOperation(sayHiOperation).init();
}
@Test
public void shouldReturnHiResponseRerunedByFirstNode() {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
Greeter greeterClient = createServiceClient(cbcFeature);
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("How you doin"));
assertThat(greeterClient.sayHi(), is("How you doin"));
}
@Test
public void shouldFailoverTo2ndNodeIfFirstNodeNotResponsing() {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
cbcFeature.setAddressList(asList("http://not-existing.com", NODE1_ENDPOINT_ADDRESS));
cbcFeature.setResetTimeout(100000);
Greeter greeterClient = createServiceClient(cbcFeature);
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Whats up?"));
assertThat(greeterClient.sayHi(), is("Whats up?"));
}
@Test
public void shouldFailoverTo2ndNodeAfterTimeoutOn1stNode() {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
cbcFeature.setFailureThreshold(1);
cbcFeature.setResetTimeout(100000l);
cbcFeature.setReceiveTimeout(800l);
Greeter greeterClient = createServiceClient(cbcFeature);
// causing timeout on the client
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
node2Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Heeey node 2"));
assertThat(greeterClient.sayHi(), is("Heeey node 2"));
}
@Test
public void shouldContinueUsingFirstNodeIfFailureThresholdNotExceeded() {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
cbcFeature.setFailureThreshold(3);
cbcFeature.setResetTimeout(100000);
cbcFeature.setReceiveTimeout(800l);
Greeter greeterClient = createServiceClient(cbcFeature);
// causing two timeouts on the client
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Heeey node 1"));
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Heeey2 node 1"));
assertThat(greeterClient.sayHi(), is("Heeey node 1"));
assertThat(greeterClient.sayHi(), is("Heeey2 node 1"));
}
@Test
public void shouldFailoverToSecondNodeAfterExceedingFailureThreshold() throws SAXException, IOException {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
// setting failure threshold to 2 so it will retry first request to the
// 1st node
cbcFeature.setFailureThreshold(2);
cbcFeature.setResetTimeout(100000);
cbcFeature.setReceiveTimeout(800l);
Greeter greeterClient = createServiceClient(cbcFeature);
// setting up timeouts of 2 first requests
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
node2Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Ho"));
assertThat(greeterClient.sayHi(), is("Ho"));
// this request fails over to 2nd node after exceeding threshold of 2
// attempts on on 1st node
Document recordedRequests1Node = node1Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests1Node, hasXPath("count(//sayHi)", equalTo("2")));
// 2nd node
Document recordedRequests2Node = node2Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests2Node, hasXPath("count(//sayHi)", equalTo("1")));
}
@Test
public void shouldContinueUsingNode2AfterFailover() throws SAXException, IOException {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
cbcFeature.setFailureThreshold(1);
cbcFeature.setResetTimeout(100000);
cbcFeature.setReceiveTimeout(800l);
Greeter greeterClient = createServiceClient(cbcFeature);
// causing timeout on node 1
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
// setting up 2 consecutive responses for node2
node2Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Ha"));
node2Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Ha ha"));
// this request fails over to node 2 after one delivery attempt to node
// 1
assertThat(greeterClient.sayHi(), is("Ha"));
Document recordedRequests1Node = node1Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests1Node, hasXPath("count(//sayHi)", equalTo("1")));
Document recordedRequests2Node = node1Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests2Node, hasXPath("count(//sayHi)", equalTo("1")));
// 2nd request goes directly to node 2
assertThat(greeterClient.sayHi(), is("Ha ha"));
recordedRequests1Node = node1Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests1Node, hasXPath("count(//sayHi)", equalTo("1")));
recordedRequests2Node = node2Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests2Node, hasXPath("count(//sayHi)", equalTo("2")));
}
@Test
public void shouldFailbackToFirstNodeAfterResetTimeout() throws InterruptedException {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
cbcFeature.setFailureThreshold(1);
long resetTimeout = 1300;
cbcFeature.setResetTimeout(resetTimeout);
cbcFeature.setReceiveTimeout(800l);
Greeter greeterClient = createServiceClient(cbcFeature);
// causing timeout on node1 (1st request)
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Heyah node1 speaking!"));
node2Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Heyah node2 speaking!"));
node2Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseText("Heyah ho node2 speaking!"));
// this request fails over to node 2
assertThat(greeterClient.sayHi(), is("Heyah node2 speaking!"));
// this request goes directly to node2
assertThat(greeterClient.sayHi(), is("Heyah ho node2 speaking!"));
// waiting till failover reset timeout elapses
Thread.sleep(resetTimeout + 300);
// failback, node1 is healthy again
assertThat(greeterClient.sayHi(), is("Heyah node1 speaking!"));
}
@Test(expected = WebServiceException.class)
public void shouldThrowExceptionAfterAllNodesFail() throws SAXException, IOException {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
cbcFeature.setFailureThreshold(1);
cbcFeature.setResetTimeout(100000);
cbcFeature.setReceiveTimeout(800l);
Greeter greeterClient = createServiceClient(cbcFeature);
// causing timeout on node1 (1st request)
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
// causing timeout on node2 (1st request)
node2Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
// the request is sent to each node and returns an error because
// no more nodes are available
try {
greeterClient.sayHi();
} finally {
Document recordedRequests1Node = node1Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests1Node, hasXPath("count(//sayHi)", equalTo("1")));
Document recordedRequests2Node = node2Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests2Node, hasXPath("count(//sayHi)", equalTo("1")));
}
}
@Test(expected = WebServiceException.class)
public void shouldFailFastAfterAllNodesFail() throws SAXException, IOException {
CircuitSwitcherClusteringFeature cbcFeature = createCircuitBreakerFeature();
cbcFeature.setFailureThreshold(1);
cbcFeature.setResetTimeout(100000);
cbcFeature.setReceiveTimeout(800l);
Greeter greeterClient = createServiceClient(cbcFeature);
// causing timeout on node1 (1st request)
node1Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
// causing timeout on node2 (1st request)
node2Controller.webserviceOperation(sayHiOperation).setUp(sayHiResponse().withResponseDelaySec(1));
// the request is sent to each node and returns an error because
// no more nodes are available
try {
greeterClient.sayHi();
} catch (WebServiceException e) {
} finally {
Document recordedRequests1Node = node1Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests1Node, hasXPath("count(//sayHi)", equalTo("1")));
Document recordedRequests2Node = node2Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests2Node, hasXPath("count(//sayHi)", equalTo("1")));
}
// reinitalising mocks to clear recorded requests
node1Controller.webserviceOperation(sayHiOperation).init();
node2Controller.webserviceOperation(sayHiOperation).init();
// next attempt fails fast, not sending any requests
try {
greeterClient.sayHi();
} finally {
Document recordedRequests1Node = node1Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests1Node, hasXPath("count(//sayHi)", equalTo("0")));
Document recordedRequests2Node = node2Controller.webserviceOperation(sayHiOperation).recordedRequests();
assertThat(recordedRequests2Node, hasXPath("count(//sayHi)", equalTo("0")));
}
}
private Greeter createServiceClient(CircuitSwitcherClusteringFeature cbcFeature) {
bean.setFeatures(asList(cbcFeature));
Greeter serviceClient = bean.create(Greeter.class);
return serviceClient;
}
}